package cn.kivensoft.xlsx.writer;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class Cell implements Comparable<Cell> {
    public enum CellType {
        STRING, NUMBER, DATE, BOOL, FORMULA, EMPTY, DEFAULT
    }
    
    public int rowAddress;
    public int columnAddress;
    public Object value;
    public CellType fieldtype = CellType.STRING;
    public Style cellStyle;
    
    public Cell() {
    	super();
    }
    
    public Cell(Object value, CellType type) {
    	this.value = value;
    	fieldtype = type;
        resolveCellType();
    }

    public Cell(Object value, CellType type, int column, int row) {
        this(value, type);
        columnAddress = column;
        rowAddress = row;
        if (type == CellType.DEFAULT) resolveCellType();
    }

    public void resolveCellType() {
        if (value == null) {
            fieldtype = CellType.EMPTY;
            value = "";
            return;
        }
         
        if (fieldtype == CellType.FORMULA || fieldtype == CellType.EMPTY)
            return;
         
        if (value instanceof Integer) fieldtype = CellType.NUMBER;
        else if (value instanceof Float) fieldtype = CellType.NUMBER;
        else if (value instanceof Double) fieldtype = CellType.NUMBER;
        else if (value instanceof Long) fieldtype = CellType.NUMBER;
        else if (value instanceof Boolean) fieldtype = CellType.BOOL;
        else if (value instanceof Date) fieldtype = CellType.DATE;
        else fieldtype = CellType.STRING; 
    }

    // Default
    public String getCellAddress() {
        return resolveCellAddress(columnAddress,rowAddress);
    }

    public Style setStyle(Style style, Workbook workbookReference) throws Exception {
        if (workbookReference == null)
            throw new Exception("No workbook reference was defined while trying to set a style to a cell");
         
        if (style == null) throw new Exception("No style to assign was defined");
         
        cellStyle  = workbookReference.addStyle(style, true);
        return cellStyle;
    }

    public void removeStyle(Workbook workbookReference) throws Exception {
        if (workbookReference == null)
            throw new Exception("No workbook reference was defined while trying to remove a style from a cell");
         
        String styleName = cellStyle.getName();
        cellStyle = null;
        workbookReference.removeStyle(styleName, true);
    }

    public static <T> List<Cell> convertArray(List<T> list) {
        List<Cell> output = new ArrayList<Cell>();
        for (T item : list) {
			Cell c;
            if (item instanceof Integer)
                c = new Cell((Integer)item, CellType.NUMBER);
            else if (item instanceof Float)
                c = new Cell((Float)item, CellType.NUMBER);
            else if (item instanceof Double)
                c = new Cell((Double)item, CellType.NUMBER);
            else if (item instanceof Long)
                c = new Cell((Long)item, CellType.NUMBER);
            else if (item instanceof Boolean)
                c = new Cell((Boolean)item, CellType.BOOL);
            else if (item instanceof Date)
                c = new Cell((Date)item, CellType.DATE);
            else if (item instanceof String)
                c = new Cell((String)item, CellType.STRING);
            else
                // Default = unspecified object
                c = new Cell((String)item, CellType.DEFAULT);       
            output.add(c);
        }
        return output;
    }

    public static List<Address> getCellRange(String range) throws Exception {
        Range r = resolveCellRange(range);
        return getCellRange(r.startAddress, r.endAddress);
    }

    public static List<Address> getCellRange(String startAddress, 
    		String endAddress) throws Exception {
        return getCellRange(resolveCellCoordinate(startAddress),
        		resolveCellCoordinate(endAddress));
    }

    public static List<Address> getCellRange(int startColumn, int startRow,
    		int endColumn, int endRow) {
		return getCellRange(new Address(startColumn, startRow),
				new Address(endColumn, endRow));
    }

    public static List<Address> getCellRange(Address startAddress, Address endAddress) {
        int startColumn, endColumn, startRow, endRow;
        if (startAddress.columnIndex < endAddress.columnIndex) {
            startColumn = startAddress.columnIndex;
            endColumn = endAddress.columnIndex;
        } else {
            startColumn = endAddress.columnIndex;
            endColumn = startAddress.columnIndex;
        } 
        if (startAddress.rowIndex < endAddress.rowIndex) {
            startRow = startAddress.rowIndex;
            endRow = endAddress.rowIndex;
        } else {
            startRow = endAddress.rowIndex;
            endRow = startAddress.rowIndex;
        } 
		List<Address> output = new ArrayList<Address>();
		for (int i = startRow; i <= endRow; i++)
			for (int j = startColumn; j <= endColumn; j++)
				output.add(new Address(j, i));
        return output;
    }

    public static Range resolveCellRange(String range) throws Exception {
        if (range == null || range.isEmpty())
            throw new Exception("The cell range is null or empty and could not be resolved");
         
        int pos = range.indexOf(':');
        if (pos <= 0 || pos >= range.length() - 1)
            throw new Exception("The cell range (" + range + ") is malformed and could not be resolved");
         
        return new Range(resolveCellCoordinate(range.substring(0, pos)),
        		resolveCellCoordinate(range.substring(pos + 1)));
    }

    public static String resolveCellAddress(int column, int row) {
        if (column >= 16384 || column < 0)
            throw new RuntimeException("The column number (" + column 
				+ ") is out of range. Range is from 0 to 16383 (16384 columns).");
        return resolveColumnAddress(column) + (row + 1);
    }

    public static int resolveRow(String name) throws Exception {
        if (name == null || name.isEmpty())
            throw new Exception("cell index must be not empty!");
         
        int number = 0;
        int pow = 1;
        for (int i = name.length() - 1; i >= 0; i--) {
            char c = name.charAt(i);
            if (c < '0' || c > '9') break;
            number += (c - '0') * pow;
            pow *= 10;
        }
        number--;
        
        if (number >= 1048576 || number < 0)
            throw new Exception("The row number (" + number 
				+ ") is out of range. Range is from 0 to 1048575 (1048576 rows).");
         
        return number;
    }

    public static int resolveColumn(String name) {
        if (name == null || name.isEmpty())
            throw new RuntimeException("cell index must be not empty!");
         
        int number = 0;
        int pow = 1;
        for (int i = name.length() - 1; i >= 0; i--) {
            char c = name.charAt(i);
            if (c >= '0' && c <= '9') continue;
            number += (c - 'A' + 1) * pow;
            pow *= 26;
        }
        number--;

        if (number >= 16384 || number < 0)
            throw new RuntimeException("The column number (" + number 
				+ ") is out of range. Range is from 0 to 16383 (16384 columns).");
         
        return number;
    }

    public static String resolveColumnAddress(int columnNumber) {
        if (columnNumber >= 16384 || columnNumber < 0)
            throw new RuntimeException("The column number (" + columnNumber 
				+ ") is out of range. Range is from 0 to 16383 (16384 columns).");
         
        // A - XFD
        int j = 0, k = 0, l = 0;
        for (int i = 0; i <= columnNumber; i++) {
            if (j > 25) { k++; j = 0; }
            if (k > 25) { l++; k = 0; }
            j++;
        }

        StringBuilder sb = new StringBuilder();
        if (l > 0) sb.append((char)(l + 64));
        if (k > 0) sb.append((char)(k + 64));
        sb.append((char)(j + 64));
        return sb.toString();
    }

    public void setCellLockedState(boolean isLocked, boolean isHidden, 
    		Workbook workbookReference) throws Exception {
        Style lockStyle;
        if (cellStyle == null) lockStyle = new Style();
        else lockStyle = cellStyle.copy(); 
        lockStyle.currentCellXf.locked = isLocked;
        lockStyle.currentCellXf.hidden = isHidden;
        setStyle(lockStyle, workbookReference);
    }

    public static Address resolveCellCoordinate(String address) throws Exception {
        return new Address(resolveColumn(address), resolveRow(address));
    }

    public static class Address {
        public int rowIndex;
        public int columnIndex;

        public Address(int columnIndex, int rowIndex) {
            this.columnIndex = columnIndex;
            this.rowIndex = rowIndex;
        }

        public String getAddress() throws Exception {
            return resolveCellAddress(columnIndex, rowIndex);
        }

        public String toString() {
			try {
				return getAddress();
			} catch (Exception e) {
				throw new RuntimeException(e);
			}
        }
    }

    public static class Range {
        public Address startAddress;
        public Address endAddress;
        
        public Range(Address start, Address end) {
            startAddress = start;
            endAddress = end;
        }

        public String toString() {
			return startAddress.toString() + ":" + endAddress.toString();
        }
    }

	@Override
	public int compareTo(Cell o) {
		if (this.rowAddress == o.rowAddress) {
			if (columnAddress == o.columnAddress) return 0;
			else if (columnAddress > o.columnAddress) return 1;
			else return -1;
        }
        else return rowAddress > o.rowAddress ? 1 : -1;
	}
}


